﻿@namespace BlazorDemos.Shared

@inject IJSRuntime JsRuntime;
@inject NavigationManager UriHelper;
@inject SampleService SampleService;

@implements IDisposable;

<div class="@SampleUtils.SEARCH_CONTAINER">
    <input @ref="inputElement" @bind="inputValue" class="@SampleUtils.SEARCH_INPUT" placeholder="Search a component or features" aria-label="Search a component or feature examples" @onfocus="OnInputFocus" @onblur="OnInputBlur" @oninput="OnInputChange" />
    <span class="@iconClass" @onclick="OnIconClick" aria-label="close" role="button"></span>
</div>
<div @ref="popupElement" class="@popupClass" @onmouseover="@OnPopupHover">
    @if (searchResult.Count > 0)
    {
        <ul class="@SampleUtils.LIST_UL_CLASS">
            @foreach (var group in searchResult)
            {
                <li class="@SampleUtils.LIST_LI_GROUP_CLASS">
                    <span>@group.Category</span>
                </li>
                @foreach (var item in group.SampleList)
                {
                    <li class="@liClass"  @onclick="@(() => { OnSampleSelect(item.SamplePath); })">
                        <span>@item.SampleName</span>
                    </li>
                }
            }
        </ul>
    }
    else
    {
        <div class="@SampleUtils.SEARCH_NO_DATA">
            <span>We're sorry. We cannot find any matches for your search term.</span>
        </div>
    }
</div>

@code {

    const int SEARCH_LENGTH = 0;
    const int CATEGORY_LENGTH = 3;
    const int SEARCH_RESULT_COUNT = 10;

    private string liClass { get; set; }
    private bool isFocusOut { get; set; }
    private string iconClass { get; set; }
    private string popupClass { get; set; }
    private string inputValue = string.Empty;
    private List<SearchList> searchData { get; set; }
    private List<SearchList> searchResult { get; set; }
    private ElementReference inputElement { get; set; }
    private ElementReference popupElement { get; set; }
    private DotNetObjectReference<object> dotnetObjectRef { get; set; }
    [CascadingParameter]
    protected MainLayout Layout { get; set; }

    // Update search result based on the search content.
    private void UpdateSearchResult(string searchContent)
    {
        // Search based on multiple input values separated by space.
        if (searchContent.Contains(SampleUtils.SPACE))
        {
            var splitContent = searchContent.Split(SampleUtils.SPACE);
            // Prevent searching when user set empty space.
            if (!splitContent.Contains(string.Empty))
            {
                foreach (var splitItem in splitContent)
                {
                    if (splitItem.Length >= SEARCH_LENGTH)
                    {
                        searchResult = GetSearchList(splitItem, splitItem != splitContent[0]);
                    }
                }
            }
        }
        // search normal content.
        else
        {
            searchResult = GetSearchList(searchContent);
        }
    }

    // Retrieve search result based on search content.
    private List<SearchList> GetSearchList(string searchContent, bool isMultiContentSearch = false)
    {
        // The below condition is used for meaningful search when entering multiple contents.
        if (searchContent.Length <= CATEGORY_LENGTH && isMultiContentSearch)
        {
            return searchResult;
        }
        var searchOutput = searchData.ToList();
        var sampleSearch = new List<SearchList>();

        // Move category group search result at top.
        var categorySearch = new List<SearchList>();
        if (searchContent.Length >= CATEGORY_LENGTH)
        {
            categorySearch = searchOutput.Where(samples => samples.Category.IndexOf(searchContent, StringComparison.OrdinalIgnoreCase) >= 0).ToList();
            if (categorySearch.Any() && !isMultiContentSearch)
            {
                categorySearch.Reverse();
                foreach (var item in categorySearch)
                {
                    searchOutput.Remove(item);
                    searchOutput.Insert(0, item);
                }
            }
        }
        // Get search source from previous search result or complete datasource of search component based on multi content search.
        foreach (var item in searchOutput)
        {
            // Search each sample name with the input value.
            var samples = WhereFilter(item, searchContent);
            if (samples.Any())
            {
                // Check whether the search result already exist by using same category name.
                var sampleInfo = searchResult.Where(info => info.Category == item.Category).ToList();
                if (sampleInfo.Any() && isMultiContentSearch && sampleInfo.FirstOrDefault().IsMultiSearch)
                {
                    sampleInfo.FirstOrDefault().IsMultiSearch = true;
                    // Set sample list without duplicate data.
                    samples = samples.Union(sampleInfo.FirstOrDefault().SampleList).ToList();
                }
                // Add the new search result to the search list.
                sampleSearch.Add(new SearchList { Category = item.Category, SampleList = samples, IsMultiSearch = true });
            }
            // Set category search result at first if it doesn't have any sample search result.
            else if(categorySearch.IndexOf(item) > 0 && !isMultiContentSearch)
            {
                sampleSearch.Add(new SearchList { Category = item.Category, SampleList = item.SampleList.Take(CATEGORY_LENGTH).ToList() });
            }
        }
        // Get the search result when there is no sample name with specified search content but the category search still have results.
        if (!sampleSearch.Any() && categorySearch.Any())
        {
            // Get the take count from the expected search result count.
            var take = Convert.ToInt32(SEARCH_RESULT_COUNT / categorySearch.Count);
            foreach (var item in categorySearch)
            {
                // Check the search result already exist and add the new items to the search list.
                var searchedItem = sampleSearch.Where(sample => sample.Category == item.Category).ToList();
                if (!searchedItem.Any())
                {
                    sampleSearch.Add(new SearchList { Category = item.Category, SampleList = item.SampleList.Take(take).ToList() });
                }
            }
        }
        return sampleSearch.Take(SEARCH_RESULT_COUNT).ToList();
    }

    // Get where filter search results.
    private List<SearchResult> WhereFilter(SearchList item, string searchContent)
    {
        return item.SampleList.Where(sample => sample.SampleName.IndexOf(searchContent, StringComparison.OrdinalIgnoreCase) >= 0).ToList();
    }

    // Show search popup element.
    private void ShowPopup()
    {
        popupClass = SampleUtils.RemoveClass(popupClass, SampleUtils.DISPLAY_NONE);
        iconClass = SampleUtils.RemoveClass(iconClass, SampleUtils.SEARCH_ICON);
        iconClass = SampleUtils.AddClass(iconClass, SampleUtils.CLEAR_ICON);
    }

    // Input value change event handler method.
    private async Task OnInputChange(ChangeEventArgs args)
    {
        isFocusOut = false;
        var searchContent = args.Value.ToString();
        // Search with input value.
        if (searchContent.Length > SEARCH_LENGTH)
        {
            UpdateSearchResult(searchContent);
            ShowPopup();
        }
        // Show popup for validation message when the search length is not meet.
        else if (searchContent.Length <= SEARCH_LENGTH)
        {
            ShowPopup();
        }
        // Hide the popup when there is empty search value.
        if (string.IsNullOrEmpty(searchContent))
        {
            await this.HidePopup();
        }
    }

    // Input text focus event handler method.
    private void OnInputFocus()
    {
        isFocusOut = false;
        iconClass = SampleUtils.RemoveClass(iconClass, SampleUtils.SEARCH_ICON);
        iconClass = SampleUtils.AddClass(iconClass, SampleUtils.CLEAR_ICON);
    }

    // Input text blur event handler method.
    private async Task OnInputBlur()
    {
        isFocusOut = true;
        // Await the popup hide process for the search list selection.
        await Task.Delay(200);
        await this.HidePopup();
    }

    // Search result selection event handler method.
    private void OnSampleSelect(string samplePath)
    {
        var themeName = SampleUtils.GetThemeName(UriHelper.Uri);
        Layout.RightPaneRef?.TabSelectedItemChange();
        UriHelper.NavigateTo(samplePath.ToLower() + "?theme=" + themeName);
    }

    // Icon click event handler method.
    private async Task OnIconClick()
    {
        isFocusOut = false;
        // Clear input value and hide the popup.
        if (iconClass.Contains(SampleUtils.CLEAR_ICON))
        {
            await InputFocus();
        }
    }

    // Popup hover event handler.
    private void OnPopupHover()
    {
        // Reset the li class if keyboard navigated in the popup.
        if (liClass.Contains(SampleUtils.SEARCH_KEY_NAV))
        {
            liClass = SampleUtils.RemoveClass(liClass, SampleUtils.SEARCH_KEY_NAV);
        }
    }

    /// <summary>
    /// Hide popup element.
    /// </summary>
    [JSInvokable]
    public async Task HidePopup(bool isEsc = false)
    {
        searchResult.Clear();
        popupClass = SampleUtils.AddClass(popupClass, SampleUtils.DISPLAY_NONE);
        iconClass = SampleUtils.RemoveClass(iconClass, SampleUtils.CLEAR_ICON);
        iconClass = SampleUtils.AddClass(iconClass, SampleUtils.SEARCH_ICON);
        if (isEsc)
        {
            await this.Layout.ShowSearchLayout(!isEsc, isEsc);
            StateHasChanged();
        }
    }

    /// <summary>
    /// Update keyboard interaction to the popup element class.
    /// </summary>
    [JSInvokable]
    public void UpdateKeyboardInteraction()
    {
        liClass = SampleUtils.AddClass(liClass, SampleUtils.SEARCH_KEY_NAV);
    }

    /// <summary>
    /// Focus the input element on search icon click from home or demo page.
    /// </summary>
    public async Task InputFocus()
    {
        await this.HidePopup();
        inputValue = string.Empty;
        // Focus the search input element after it visible.
        await Task.Delay(100);
        await JsRuntime.InvokeVoidAsync("sfBlazorSB.inputFocus", inputElement);
    }

    /// <summary>
    /// Specifies the input element focusout status.
    /// </summary>
    public bool IsFocusOut()
    {
        return isFocusOut;
    }

    protected override void OnInitialized()
    {
        base.OnInitialized();
        searchResult = new List<SearchList>();
        searchData = new SearchList().GetSearchList();
        iconClass = SampleUtils.SEARCH_ICON;
        liClass = SampleUtils.LIST_LI_CLASS;
        popupClass = SampleUtils.SEARCH_POPUP + SampleUtils.SPACE + SampleUtils.DISPLAY_NONE;
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            dotnetObjectRef = DotNetObjectReference.Create<object>(this);
            await JsRuntime.InvokeVoidAsync("sfBlazorSB.wireSearchEvents", dotnetObjectRef, inputElement, popupElement);
        }
    }

    public void Dispose()
    {
        dotnetObjectRef?.Dispose();
    }
}